﻿
/*
 * Copyright (C) 2011  Roy Sanchez
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * 
 * +
 * if you find any bug or just have a question, can email me.
 * email : roygold8@gmail.com
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;


namespace FTP_Client
{
    public class DirectoryMapper
    {

        #region Variables

        /*
         * Dentro de cada nodo del directorio en su variagble Tag, se le 
         * guarda el path.
         */

        bool StartDirectory = false; // dice si el directorio k se mapea es el primero
        TreeView Directory = null;
        TreeView DirectoryR = null;
        FTP FtpClient = null;
        ImageList ListaImagenes; //aqui guardamos los iconos 

        bool Canceled = false;

        string Pc_CurentDirectory = string.Empty;
        string Pc_Root = string.Empty;

        string Rm_CurrentDirectory = string.Empty;

        /*
         * Los dummys son nodos que no tienen ninguna informacion que se utilizan
         * para poner el simbolo de '+' en las carpetas y discos, cuando este simbolo
         * es presionado, entonces se verifica si realmente existen sub-directorios.
         * ----- ayuda para no tener k cargar cada directorio y verificar si tiene
         * subdirectorios innecesariamente ------
         */ 
        string dummy = "DUMMY"; 

        /*
         * Para ahorrar espacio en la lista de imagenes, solo guardamos una copia por cada
         * extension, esto es bueno cuando son imagenes comunes, ej: documentos word, excel, pdf, etc...
         * asi como imagenes y la mayoria de las extensiones, pero ahi algunas que no utilizan el mismo
         * icono para todo, por ejemplo las extenciones 'EXE' y 'LNK', para lidear con esta se creo la lista
         * de extensiones especiales, aqui si un archivo pertenece a una de estas extensiones su icono es almacenado
         * solo para el.
         */
        List<string> GeneralExts;

        #endregion Variables

        #region Metodos

        public string getCurrentPCdirectory()
        {
            return Pc_CurentDirectory;
        }

        public string getCurrentRMdirectory()
        {
            return Rm_CurrentDirectory;
        }

        public void ClearPCCurrentDirectory()
        {
            Pc_CurentDirectory = string.Empty;
        }

        public void ClearRMCurrentDirectory()
        {
            Rm_CurrentDirectory = string.Empty;
        }


        public void setFtpClient(FTP _FtpClient)
        {
            FtpClient = _FtpClient;
        }

        public TreeView Local()
        {
            return Directory;
        }

        public TreeView Remoto()
        {
            return DirectoryR;
        }

        public bool isCanceled()
        {
            //bool tmp = Canceled;
            return Canceled;
        }

        public void StopCancel()
        {
            Canceled = false;
        }

        #endregion Metodos



        //recibe el treeview donde se desplegara la informacion del directorio
        #region Constructores

        public DirectoryMapper(TreeView _DirectoryLocal, TreeView _DirectoryRemoto)
        {
            Directory = _DirectoryLocal;
            DirectoryR = _DirectoryRemoto;
            BaseImages();
            ExtsListFill();
            Directory.ImageList = ListaImagenes;
            DirectoryR.ImageList = ListaImagenes;

            Directory.BeforeExpand += new TreeViewCancelEventHandler(Directory_BeforeExpand);
            Directory.AfterSelect += new TreeViewEventHandler(Directory_AfterSelect);
            DirectoryR.BeforeExpand += new TreeViewCancelEventHandler(DirectoryR_BeforeExpand);
            DirectoryR.AfterSelect += new TreeViewEventHandler(DirectoryR_AfterSelect);
        }

        public DirectoryMapper(TreeView _DirectoryLocal, TreeView _DirectoryRemoto, FTP _FtpClient)
        {

            Directory = _DirectoryLocal;
            DirectoryR = _DirectoryRemoto;

            
            BaseImages();
            ExtsListFill();

            Directory.ImageList = ListaImagenes;
            DirectoryR.ImageList = ListaImagenes;
            FtpClient = _FtpClient;

            Directory.BeforeExpand += new TreeViewCancelEventHandler(Directory_BeforeExpand);
            Directory.AfterSelect += new TreeViewEventHandler(Directory_AfterSelect);
            DirectoryR.BeforeExpand += new TreeViewCancelEventHandler(DirectoryR_BeforeExpand);
            DirectoryR.AfterSelect += new TreeViewEventHandler(DirectoryR_AfterSelect);

            Directory.ItemDrag += new ItemDragEventHandler(Directory_ItemDrag);
            Directory.AllowDrop = true;
            
            Directory.DragLeave += new EventHandler(Directory_DragLeave);
            Directory.DragDrop += new DragEventHandler(Directory_DragDrop);
            Directory.DragOver += new DragEventHandler(Directory_DragOver);
            
            //Directory.Dr
        }

        void Directory_DragOver(object sender, DragEventArgs e)
        {
            //throw new NotImplementedException();
        }

        void Directory_ItemDrag(object sender, ItemDragEventArgs e)
        {
            //throw new NotImplementedException();
        }

        void Directory_DragDrop(object sender, DragEventArgs e)
        {
            //throw new NotImplementedException();
        }

        void Directory_DragLeave(object sender, EventArgs e)
        {
            
            //throw new NotImplementedException();
        }

        #endregion Constructores

        void Directory_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (FtpClient != null && !FtpClient.VerifyConnection())
            {
                Canceled = true;
                return;
            }

            TreeNode Node = e.Node;
            File_Info info = (File_Info)Node.Tag;

            Pc_CurentDirectory = info.getPath();

            //throw new NotImplementedException();
        }


        void Directory_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            TreeNode Node = e.Node;
            TreeNode Dummy = Node.LastNode;
            File_Info info = (File_Info)Dummy.Tag;

            if (info.getPath() == dummy)
            {
                Node.Nodes.Remove(Dummy);
                PC_Folders(Node);
                PC_Files(Node);
            }
            else
            {
                PC_Folders(Node);
                PC_Files(Node);
            }


            info = (File_Info)Node.Tag;
            Pc_CurentDirectory = info.getPath();

        }

        void DirectoryR_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (FtpClient != null   && !FtpClient.VerifyConnection())
            {
                Canceled = true;
                return;
            }

            TreeNode Node = e.Node;
            File_Info Info = (File_Info)Node.Tag;

            Rm_CurrentDirectory = Info.getPath();

            TreeViewCancelEventArgs Event = new TreeViewCancelEventArgs(Node, false, TreeViewAction.Expand);

            DirectoryR_BeforeExpand(sender, Event);

        }

        void DirectoryR_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {

            if (StartDirectory || !FtpClient.VerifyConnection())
                return;

            TreeNode Node = e.Node;
            File_Info Info = (File_Info)Node.Tag;//info del node parent

            if (Info.getRights() != string.Empty && Info.getRights()[0] != 'd') return;


            TreeNode Dummy = Node.LastNode;
            File_Info inf = new File_Info();
            if (Dummy != null)
                inf = (File_Info)Dummy.Tag;

            if (inf.getPath() == dummy)
            {
                Node.Nodes.Remove(Dummy);
            }


            //cambia al directorio seleccionado
            if (FtpClient.getCurrentDirectory() != Info.getPath())
                if(!FtpClient.ChangeWorkingDirectory(Info.getPath()))
                    return;

            //genera la lista de los folders
            FtpClient.DirectoryList();

            

            string temp2 = FtpClient.getPasv_Response().Replace("\r", "");
            FtpClient.ClearPasv_Response();
            string[] Datos1 = temp2.Split(new char[]{'\n'}, StringSplitOptions.RemoveEmptyEntries);

            bool finalizo = false;

            foreach (string temp in Datos1)
            {
                if (temp == "") continue;

                string[] date = new string[3];
                string[] Datos2 = temp.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
                Info = new File_Info();
                for (int i = 0, ii = 0; i < Datos2.Length; i++)
                {
                    if (!string.IsNullOrEmpty(Datos2[i]))
                    {
                        switch (ii)
                        {
                            case 0:
                                Info.setRights(Datos2[i]);
                                break;
                            case 1:
                                Info.setLink(Convert.ToInt32(Datos2[i]));
                                break;
                            case 2:
                                Info.setOwner(Datos2[i]);
                                break;
                            case 3:
                                Info.setGroup(Datos2[i]);
                                break;
                            case 4:
                                Info.setSize(Convert.ToInt32(Datos2[i]));
                                break;
                            case 5:
                                date[0] = Datos2[i];
                                break;
                            case 6:
                                date[1] = Datos2[i];
                                break;
                            case 7:
                                date[2] = Datos2[i];
                                break;
                            case 8:
                                Info.setName(Datos2[i]);
                                finalizo = true;
                                break;
                            default:
                                Info.setName(Info.getName() + ' ' + Datos2[i]);
                                break;
                        }

                        ii++;
                    }
                }

                //no termino, posible razon el string esta mal formado
                if (finalizo == false)
                    return;

                if (Info.getName() == null)
                    return;

                DateTime Fecha = DateTime.Now; ;
                if (date[2].Split(':').Length > 1)
                {
                    DateTime.TryParse(date[0] + " " + date[1], out Fecha);
                    Fecha = Fecha.Add(TimeSpan.Parse(date[2]));
                }
                else
                {
                    DateTime.TryParse(date[0] + " " + date[1] + " " + date[2], out Fecha);
                }


                Info.setDate(Fecha);

                char FileorDirectory = Info.getRights()[0];

                if (FileorDirectory == '-')
                {
                    Info.isFile = true;
                    Info.setPath(FtpClient.getCurrentDirectory() + Info.getName());
                    RM_File(Node, Info.getName(), Info);
                }
                else if (FileorDirectory == 'd')
                {
                    Info.isFolder = true;
                    RM_Folders(Node, Info.getName(), true, Info);
                }

            }

        }


        public bool ClearRM_Directory()
        {
            DirectoryR.Nodes.Clear();

            return true;
        }


        public void UpdateCurrentRMDirectory()
        {

            TreeNode Node = DirectoryR.SelectedNode;

            DirectoryR_AfterSelect(null, new TreeViewEventArgs(Node, TreeViewAction.Expand));

        }

        public void UpdateCurrentPCDirectory()
        {
            TreeNode Node = Directory.SelectedNode;

            Directory_BeforeExpand(null, new TreeViewCancelEventArgs(Node, false, TreeViewAction.Expand));
        }

        #region PCDirectory

        /*
         * Esta es la funcion principal para el directorio local, aqui conseguimos,
         * los discos, verificamos si es posible su escritura, y si es posible son
         * agregados al Tree_Vew
         */


        public bool PC_HomeDirectory()
        {
            string homefolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

            string[] path = homefolder.Split('\\');

            //string tmp2 = path[0] + "\\";
            
            TreeNode Parent = Directory.Nodes.Find(path[0] + "\\", false)[0];
            TreeViewCancelEventArgs e = new TreeViewCancelEventArgs(Parent, false, TreeViewAction.Expand);
            Directory_BeforeExpand(null, e);
            //Directory_AfterSelect(null, e);

            int i = 1;
            string tmp = string.Empty;
            /*
            foreach (TreeNode no in Parent.Nodes)
            {
                tmp = no.Name;
            }
            */
            while (i < path.Length - 1)
            {
                tmp = path[i] + "\\";
                Parent = Parent.Nodes.Find(tmp, false)[0];
                e = new TreeViewCancelEventArgs(Parent, false, TreeViewAction.Expand);
                Directory_BeforeExpand(null, e);
                i++;
            }

            Directory.SelectedNode = Parent; 
            Directory.Focus();
            Parent.Expand();

            return false;
        }

        public bool PC_Directory()
        {

            string[] Drives = Environment.GetLogicalDrives();

            foreach (string driv in Drives)
            {
                DriveInfo Info = new DriveInfo(driv);

                if (Info.IsReady)
                {

                    TreeNode Node = new TreeNode(Info.Name, 3, 3);
                    //
                    Node.Name = Info.Name;
                    //
                    File_Info info = new File_Info();
                    info.setPath(driv);
                    info.setName(driv);
                    info.setSize(Info.TotalSize);

                    Pc_CurentDirectory = driv;
                    Pc_Root = driv;

                    Node.Tag = info;

                    Directory.Nodes.Add(Node);

                    TreeNode Dummy = new TreeNode(dummy);
                    info = new File_Info();
                    info.setPath(dummy);
                    Dummy.Tag = info;

                    Node.Nodes.Add(Dummy);
                }
            }
            if (Directory.Nodes.Count > 0) //si no leyo ningun disco posible de escritura.
                return true;
            else
                return false;
        }


        /*
         * Al igual que la funcion anterior, esta consigue todos los folders(directorios)
         * del padre, recibe el nodo padre (al cual se le asignaran los sub-direcotorios)
         */
        public bool PC_Folders(TreeNode Parent)
        {

            File_Info info = (File_Info)Parent.Tag;
            string path = info.getPath();

            string[] Folders;
            try
            {
                Folders = System.IO.Directory.GetDirectories(path);
            }
            catch (Exception)
            {
                return false;
            }

            foreach (string folder in Folders)
            {
                //solo se queda con el nombre del folder
                string folderR = folder.Remove(0, path.Length);
                TreeNode Node = new TreeNode(folderR, 2, 2);
                //
                Node.Name = folderR + "\\";
                //
                //agrega el path al tag del nodo
                info = new File_Info();
                info.setPath(folder + "\\");
                info.setName(folderR);
                info.isFolder = true;
                

                Node.Tag = info;

                TreeNode Verifica = CompareNodes(Parent, Node);

                if (Verifica != null)
                {
                    //Verifica.Remove();
                    return false;
                    //Parent.Nodes.Remove(Verifica);
                }


                Parent.Nodes.Add(Node);

                //crea el dummy para que le salga el signo de '+'
                TreeNode Dummy = new TreeNode(dummy);
                info = new File_Info();
                info.setPath(dummy);
                Dummy.Tag = info;

                Node.Nodes.Add(Dummy);
            }

            return true;

        }


        /*
         * Al igual que el anterior, consigue todos los archivos que contiene el directorio
         * padre (parent) el cual recibe. --- ahi que agregar los otros datos de los archivos como
         * permisos, etc..
         */
        public bool PC_Files(TreeNode Parent)
        {

            File_Info info = (File_Info)Parent.Tag;
            string path = info.getPath();

            string[] Files;
            try
            {
                Files = System.IO.Directory.GetFiles(path);
            }
            catch (Exception)
            {
                return false;
            }

            foreach (string file in Files)
            {
                //le quita el resto del path y solo deja el nombre
                string fileR = file.Remove(0, path.Length);

                FileInfo inf = new FileInfo(file);

                



                //consigue la extension del archivo
                string[] exts = file.Split('.');
                string ext = exts[exts.Length - 1];


                //si la extension esta en la lista de exclusion entonces
                //almacena el icono como unico par ese archivo.
                if (GeneralExts.Contains(ext.ToUpper()))
                {
                    ext = fileR;
                }

                //agrega la imagen a la lista si no existe
                if (!ListaImagenes.Images.ContainsKey(ext))
                {
                    ListaImagenes.Images.Add(ext, Icon.ExtractAssociatedIcon(file));
                }

                TreeNode Node = new TreeNode(fileR, ListaImagenes.Images.IndexOfKey(ext),
                        ListaImagenes.Images.IndexOfKey(ext));
                //
                Node.Name = fileR;
                //
                info = new File_Info();
                //como file tiene el path completo, lo utiliza como name

                info.setPath(file);
                info.setName(fileR);
                info.setSize(inf.Length);
                info.setDate(inf.LastWriteTime);
                info.isFile = true;

                Node.Tag = info;

                TreeNode Verifica = CompareNodes(Parent, Node);

                if (Verifica != null)
                {
                    //Verifica.Remove();
                    return false;
                    //Parent.Nodes.Remove(Verifica);
                }

                Parent.Nodes.Add(Node);

            }

            return true;

        }

        #endregion PCDirectory

        #region RMDirectory

        public bool RM_HomeDirectory()
        {
            if (FtpClient == null)
                return false;

            FtpClient.PrintWorkingDirectory();

            StartDirectory = true;
            string path = FtpClient.getResponse();

            int a = path.IndexOf('\"');
            int b = path.IndexOf('\"', a + 1);

            path = path.Substring(a + 1, b - a - 1);

            string[] folders;
            if (path != @"/")
                folders = path.Split('/');
            else
                folders = new string[] { "" };


            TreeNode Node = new TreeNode();

            foreach (string folder in folders)
            {
                if (folder == string.Empty)
                {
                    Node = new TreeNode("/", 2, 2);
                    File_Info info = new File_Info();
                    info.setPath("/");
                    info.setName("/");
                    info.isFolder = true;
                    Node.Tag = info;
                    //
                    Node.Name = "/";
                    //
                    DirectoryR.Nodes.Add(Node);
                }
                else
                {
                    RM_Folders(Node, folder, false, null);
                    Node = Node.FirstNode;
                }
            }

            File_Info tmp = (File_Info)Node.Tag;

            FtpClient.setParentDirectoryPath(tmp.getPath());
            DirectoryR.ExpandAll();
            StartDirectory = false;

            Rm_CurrentDirectory = FtpClient.getCurrentDirectory();

            return true;
        }


        /*
         * Aqui se agregan al tree_view la lista de archivos que ahi en el directorio remoto
         */
        public bool RM_File(TreeNode Parent, string name, File_Info Info)
        {
            File_Info temp = (File_Info)Parent.Tag;
            string path = temp.getPath(); //el path hacia el padre

            
            string[] exts = name.Split('.');
            string ext = exts[exts.Length - 1];

            if (ext.ToUpper() == "EXE")
            {
                ext = "Generic_Application";
            }
            else if (GeneralExts.Contains(ext.ToUpper()))
            {
                ext = "Generic_Document";
            }

            if (ListaImagenes.Images.ContainsKey(ext) == false)
                ext = "Generic_Document";

            TreeNode Node = new TreeNode(name, ListaImagenes.Images.IndexOfKey(ext),
                        ListaImagenes.Images.IndexOfKey(ext));
            //
            Node.Name = name;
            //
            if (Info == null)
            {
                Info = new File_Info();
                Info.setPath(path + name);
                Info.setName(name);
                Info.isFile = true;
            }

            
            //agrega el path al tag del nodo
            Node.Tag = Info;

            TreeNode Verifica = CompareNodes(Parent, Node);

            if (Verifica != null)
                return false;


            Parent.Nodes.Add(Node);

            return true;
        }


        /*
         * Aqui agrego el nodo del folder en el lado remoto (Server_FTP), recibe su nodo 
         * parent, el nombre del folder, un bool diciendo si tiene dummy nodes y FileInfo
         * una clase especial con informacion sobre la carpeta.
         */
        public bool RM_Folders(TreeNode Parent, string name, bool DummyNode, File_Info Info)
        {
            File_Info temp = (File_Info)Parent.Tag;
            string path = temp.getPath(); //el path hacia el padre

            TreeNode Node = new TreeNode(name, 2, 2);

            if (Info == null)
            {
                Info = new File_Info();
                Info.setPath(path + name + "/");
                Info.setName(name);
                Info.isFolder = true;
            }
            else
                Info.setPath(path + Info.getName() + "/");

            //
            Node.Name = name + "/";
            //

            //agrega el path al tag del nodo
            //Info.isFolder = true;
            Node.Tag = Info;

            
            TreeNode Verifica = CompareNodes(Parent, Node);

            if (Verifica != null)
            {
                //Verifica.Remove();
                return false;
                //Parent.Nodes.Remove(Verifica);
            }
            


            Parent.Nodes.Add(Node);

            if (DummyNode)
            {
                //crea el dummy para que le salga el signo de '+'

                TreeNode Dummy = new TreeNode(dummy);
                File_Info info = new File_Info();
                info.setPath(dummy);
                Dummy.Tag = info;


                Node.Nodes.Add(Dummy);
            }
            
            return true;
        }

        #endregion RMDirectory

        #region General

        public bool ChangeToParentDirectory()
        {
            FtpClient.ChangeDirectoryToParent(); //CDUP
            string tmp = FtpClient.getParentDirectoryPath();
            string[] path = FtpClient.getParentDirectoryPath().Split('/');


            TreeNode Parent = DirectoryR.Nodes.Find(path[0] + "/", false)[0];
            int i = 1;
            while (i < path.Length - 1)
            {
                tmp = path[i] + "/";
                Parent = Parent.Nodes.Find(tmp, false)[0];
                i++;
            }

            DirectoryR.SelectedNode = Parent;
            DirectoryR.Focus();
            return true;
        }

        /*
         * Recibe el nodo padre y el posible nodo hijo, verifica si el padre
         * ya tiene un nodo hijo identico a este, de ser hasi, entonces devuelve 
         * el hijo que existe.
         */
        TreeNode CompareNodes(TreeNode Parent, TreeNode Son)
        {
            File_Info inf = (File_Info)Son.Tag;

            foreach (TreeNode node in Parent.Nodes)
            {
                if (node.Text == Son.Text)
                {
                    File_Info info = (File_Info)node.Tag;
                    if (inf.Equals(info))
                        return node;
                    else
                    {
                        if (inf.CompareTo(info) == 1)
                            node.Tag = inf;
                        else
                            node.Tag = info;
                        return node;
                    }
                }
            }

            return null;
        }


        /*
         * Aqui almacenamos, los iconos base del directorio, el cual son folder y el disco duro
         * ya que estos no pueden ser sacados de estos directamente, si usar el shell.
         */
        void BaseImages()
        {
            ListaImagenes = new ImageList();
            try
            {
                string[] all = System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceNames();

                //Image.FromStream
                ListaImagenes.ColorDepth = ColorDepth.Depth32Bit;

                ListaImagenes.Images.Add("CD_Drive", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.CD_Drive.ico")));   //0
                ListaImagenes.Images.Add("Folder_Open", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.folder_open.ico")));  //1
                ListaImagenes.Images.Add("Folder_Stuffed", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.Folder_stuffed.ico")));  //2
                ListaImagenes.Images.Add("Hard_Drive", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.Hard_Drive.ico"))); //3
                ListaImagenes.Images.Add("Generic_Document", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.Generic_Document.ico"))); //4
                ListaImagenes.Images.Add("Generic_Application", new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
                    GetManifestResourceStream("FTP_Client.Icons.Generic_Application.ico"))); //5
            }
            catch (Exception)
            {
            }
        }

        /* 
         * aqui llenamos la lista de extensiones especiales, son especiales
         * porque no todos los archivos que terminan en esta extension tienen
         * el mismo icono.
         */
        void ExtsListFill()
        {
            GeneralExts = new List<string>();
            GeneralExts.Add("EXE");
            GeneralExts.Add("LNK");
            GeneralExts.Add("URL");
            GeneralExts.Add("ICO");
        }


        #endregion General

    }
}
